/*
 * This file is part of FinVis.
 *
 * FinVis is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FinVis is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with FinVis.  If not, see <http://www.gnu.org/licenses/>
 *
 * @author Stephen Rudolph
 */

#include <QApplication>
#include <QtSql>

#include "FinException.h"
#include "FinInvestment.h"
#include "FinPortfolio.h"
#include "FinRecorder.h"
#include "FinScenario.h"
#include "FinWindow.h"

extern int currentScenario;
extern int finishedScenarios[6];
extern FinScenario scenario;
extern FinPortfolio scenarioPortfolio;

struct LogRecord
{
    QString command;
    int value;
    int scenarioType;
    int scenarioNumber;
    double portfolioRisk;
    double portfolioReturn;
    QDateTime timeStamp;
};

FinRecorder::FinRecorder(QObject *parent) :
    QObject(parent)
{
    this->isReloading = false;
    this->databaseFile = QString::fromStdString("interaction.db");
    db = QSqlDatabase::addDatabase(QString("QSQLITE"), "interaction");
    db.setDatabaseName(databaseFile);
    if (db.open())
    {
        db.exec("CREATE TABLE IF NOT EXISTS Log (Command TEXT, Value INTEGER, ScenarioType INTEGER,"
                " ScenarioNumber INTEGER, PortfolioRisk REAL, PortfolioReturn REAL, "
                " TimeStamp DATETIME DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')))");
        printf("%s\n", db.lastError().text().toStdString().c_str());
    }
    else
    {
        throw new FinException();
    }
}

FinRecorder::~FinRecorder()
{
    if (db.isOpen())
    {
        db.close();
        QSqlDatabase::removeDatabase(db.connectionName());
    }
}

void FinRecorder::clear()
{
    db.exec("DELETE FROM Log");
}

bool FinRecorder::hasHistory()
{
    QSqlQuery query = db.exec("SELECT COUNT(*) FROM Log");
    return query.next() && query.value(0).toInt() > 0;
}

void FinRecorder::recordAction(QString command, int value)
{
    if (!this->isReloading)
    {
        QSqlQuery query = QSqlQuery(db);
        query.prepare("INSERT INTO Log (Command, Value, ScenarioType, ScenarioNumber, PortfolioRisk"
                      ", PortfolioReturn) VALUES (:command, :value, :type, :number, :risk, :return)");
        query.bindValue(":command", command);
        query.bindValue(":value", value);
        query.bindValue(":type", finishedScenarios[currentScenario]);
        query.bindValue(":number", currentScenario);
        query.bindValue(":risk", scenarioPortfolio.getAggregatePercentRisk());
        query.bindValue(":return", scenarioPortfolio.getAggregatePercentReturn());
        query.exec();
    }
}

void FinRecorder::loadState()
{
    this->isReloading = true;
    QSqlQuery query = db.exec("SELECT * FROM Log ORDER BY TimeStamp ASC");
    QList<LogRecord> *commandList = new QList<LogRecord>();
    while (query.next())
    {
        commandList->append((LogRecord)
        {
            query.value(0).toString(),
            query.value(1).toInt(),
            query.value(2).toInt(),
            query.value(3).toInt(),
            query.value(4).toDouble(),
            query.value(5).toDouble(),
            query.value(6).toDateTime()
        });
    }

    // Import into scenario from beginning
    for (int i = 0; i < commandList->count(); i++)
    {
        LogRecord current = commandList->at(i);
        if (current.command == "BeginScenario")
        {
            scenario.startScenario();
        }
        else if (current.command == "EndScenario")
        {
            scenario.stopScenario();
        }
        else if (current.command == "SampleResult")
        {
            // Nothing to do
        }
        else if (current.command == "FinalResult")
        {
            // Nothing to do
        }
        else if (current.command == "SelectInvestment")
        {
            scenarioPortfolio.setSelectedID(current.value);
        }
        else if (current.command == "AddInvestment")
        {
            FinInvestment *investment = new FinInvestment();
            investment->setInvestmentOptionVectorIndex(current.value);
            scenarioPortfolio.addAndSelectInvestment(investment);
        }
        else if (current.command == "RemoveInvestment")
        {
            scenarioPortfolio.removeSelectedInvestment();
        }
        else if (current.command == "SetPrincipal")
        {
            FinInvestment *investment = scenarioPortfolio.selectedInvestment();
            investment->setPrincipal(current.value);
            scenarioPortfolio.setSelectedInvestment(*investment);
        }
        else if (current.command == "SetStartYear")
        {
            FinInvestment *investment = scenarioPortfolio.selectedInvestment();
            investment->setStartDate(QDate(current.value, 1, 1));
            scenarioPortfolio.setSelectedInvestment(*investment);
        }
        else if (current.command == "SetType")
        {
            FinInvestment *investment = scenarioPortfolio.selectedInvestment();
            investment->setInvestmentOptionVectorIndex(current.value);
            scenarioPortfolio.setSelectedInvestment(*investment);
        }
        else if (current.command == "ConfidenceLevel")
        {
            // Nothing to do
        }
        else if (current.command == "VizualizationType")
        {
            // Nothing to do
        }
        else if (current.command == "InvestmentChanged")
        {
            // Nothing to do
        }
    }

    this->isReloading = false;
    if (commandList->count() > 0)
    {
        this->recordAction("Restore", commandList->count());
    }
}

void FinRecorder::finishRecording()
{
    db.close();
    QSqlDatabase::removeDatabase(db.connectionName());
    QDir *dir = new QDir();
    QString uuid = QUuid::createUuid().toString();
    dir->rename(this->databaseFile, uuid.remove('{').remove('}').remove('-') + ".db");
    delete dir;
}
